#include <getopt.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdint.h>
#include <assert.h>

#include "cachelab.h"

typedef struct {
    int vaild;
    int tag;
    int time; //此处应该是高速缓冲块，但是该题没有要求存储，且要求加入LRU替换算法，所以包含一个time表示访问时间间隔
} CacheLine, *CacheSet, **Cache;

int s, E, b, t, S; //参考模拟器参数
int hit_count, miss_count, eviction_count; //命中、漏掉和驱逐
char filePath[100]; //文件指针
Cache cache; //开辟空间

/*动态分配缓存空间*/
void mallocCache() {
    if (s < 0) {
        printf("Not s!!!");
        exit(0);
    }

    cache = (Cache)malloc(S * sizeof(CacheSet));
    assert(cache);

    for (int i = 0; i < S; i++) {
        cache[i] = (CacheSet)malloc(E * sizeof(CacheLine));
        assert(cache[i]);
        memset(cache[i], 0, sizeof(CacheLine) * E); //初始化值，全置零
    }
}

/*释放空间*/
void freeCache() {
    for (int i = 0; i < S; ++i) {
        free(cache[i]);
    }
    free(cache);
}

/*更新访问时间*/
void lruUpdate() {
    for (int i = 0; i < S; ++i) {
        for (int j = 0; j < E; ++j) {
            if (cache[i][j].vaild) {
                cache[i][j].time++;
            }
        }
    }
}

/*模拟cache行为*/
void cacheOperation(uint64_t address) {

    uint64_t setIndex = ((1ULL << 63) - 1) >> (63 - s); //组索引位（这句我也解释的不好，就知道是跟计算机位数和最高位数有关）
    int tagIndex = address >> (b + s); //标记位（计算方法：物理地址address-(b+s)）

    CacheSet cacheSet = cache[(address >> b) & setIndex];

    for (int i = 0; i < E; ++i) {
        /*看看匹没匹配，有效位是否存在和标记位是否相同*/
        if (cacheSet[i].vaild && cacheSet[i].tag == tagIndex) {
            hit_count++; //命中
            cacheSet[i].time = 0;
            return;
        }
    }

    miss_count++; //否则就是没命中

    /*取出块，并把这个块存储到组中，并返回*/
    /*存在空位，写入*/
    for (int i = 0; i < E; ++i) {
        if (!cacheSet[i].vaild) {
            cacheSet[i].vaild = 1; //有效位设置为1
            cacheSet[i].tag = tagIndex;
            cacheSet[i].time = 0;
            return;
        }
    }

    /*没有空位，使用LRU算法进行替换*/
    eviction_count++;
    int evictIndex = 0;
    int maxTime = 0;

    for (int i = 0; i < E; ++i) {
        if (cacheSet[i].time > maxTime) {
            maxTime = cacheSet[i].time;
            evictIndex = i;
        }
    }
    cacheSet[evictIndex].tag = tagIndex;
    cacheSet[evictIndex].time = 0;
}

/*读取给的trace文件*/
void readTraceFile() {
    FILE* file = fopen(filePath, "r");
    assert(file);

    /*检测文件是否存在*/
    if (file == NULL) {
        printf("NO File!!!");
        exit(0);
    }

    char type;//虚拟内存的类型
    uint64_t address;//虚拟内存访问地址
    int size; //虚拟操作访问的字节数

    /*只要还有值，就统统放入缓存中*/
    /*注意%c前面有个空格，因为只有I没有空格，但是I表示指令加载没啥用*/
    while (fscanf(file, " %c %lx,%d", &type, &address, &size) > 0) {
        /*按官方的解释，分为M、L、S分别讨论*/
        switch (type) {
            case 'M' :
                cacheOperation(address); //M是数据修改
            case 'L' : //L是数据加载，没啥用
            case 'S' :
                cacheOperation(address); //S是数据存储
                break;
        }
        lruUpdate();
    }
    fclose(file);
}

int main(int argc, char* argv[]) //由于使用了getopt函数，需要传入
{
    /*仿写getopt函数的使用*/
    int ch;

    /*getopt（）函数还有搜索值，不返回-1*/
    while ((ch = getopt(argc, argv, "s:E:b:t:")) != -1) {
        switch (ch) {
            case 's':
                s = atoi(optarg);
                S = (int)pow(2, s); //根据定义，S = 2^s
                break;
            case 'E':
                E = atoi(optarg);
                break;
            case 'b':
                b = atoi(optarg);
                break;
            case 't':
                t = atoi(optarg);
                strcpy(filePath, optarg);
                break;
            default:
                printf("ERROR!!!");
                break;
        }
    }

    mallocCache();
    readTraceFile();
    freeCache();

    printSummary(hit_count, miss_count, eviction_count);
    return 0;
}
